/**	@file CMessageHook.cpp
 *
 *	Implementation file for CMessageHook.
 *
 *	@author JaeWook Choi
 *	@version 1.0
 *
 *	<b><i>This software is provided "as is" without express or implied warranty. Use it at your own risk!</i></b>
 *
 */
#include "stdafx.h"
#include "MessageHook.h"

 /** The CMessageHook::CMessageHook() function
 *
 *	c'tor
 *
 *	@param bAutoDelete [in] a parameter of type BOOL	Specifies whether or not the class should be automatically deleted on receiving WM_NCDESTROY
 */
CMessageHook::CMessageHook(BOOL bAutoDelete/* = FALSE*/)
	: m_bAutoDelete(bAutoDelete), m_hWnd(NULL), m_pfnSuperWindowProc(::DefWindowProc), m_bHook(FALSE)
{
}

CMessageHook::~CMessageHook()
{
	Unhook();
}

/**
 *	CMessageHook::Hook()
 *
 *	@param HWND hWnd [in] specifies the window handle which is being hooked (subclassed)
 *
 *	@return BOOL TRUE if the hook was installed successfully
 *
 */
BOOL CMessageHook::Hook(HWND hWnd)
{
	if (hWnd == NULL || (hWnd && !::IsWindow(hWnd)))
	{
		//	TRACE(_T("warning: %s (%d) m_hWnd (%x) is not null or hWnd(%x) is not valid window\n"), __FILE__, __LINE__);
		return FALSE;
	}
	
	m_thunk.Init(WindowProc, this);
	WNDPROC pProc = (WNDPROC)& (m_thunk.thunk);
	WNDPROC pfnWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
	if (pfnWndProc == NULL)
		return FALSE;
	
	m_pfnSuperWindowProc = pfnWndProc;
	m_hWnd = hWnd;

	return ( m_bHook = TRUE );
}

/**
 *	CMessageHook::Unhook()
 *
 *
 *	@return HWND the window handle from which the hook was being uninstalled
 *
 */
HWND CMessageHook::Unhook()
{
	HWND hWnd = m_hWnd;
	
	// When an application subclasses a subclassed window, it must remove the subclasses in the reverse order they were performed.
	// If the removal order is not reversed, an unrecoverable system error may occur.
	if (!IsHooked())
	{
		//	TRACE(_T("\nwarning: %s (%d) CMessageHook::Unhook() - Unsubclassing a unsubclassed window or Unsubclassing of the multiple subclasssed window is not in order.\n\n"), __FILE__, __LINE__);
		// leave WNDPROC address unchanged as it was assigned from subclassing ( call to hook() ) thus our object (CMessageHook) will be lasting 
		// till window's terminiation (either on exiting out of scope or on receiving WM_NCDESTROY with m_bAutoDelete being set)
		// As a result, any other C++ object that may points and/or stores our m_thunk.thunk address by successive subclassing will
		// behavior valid therefore any message directed to our m_thunk.thunk will be correctly redirected to m_pfnSuperWindowProc
		// (original WNDPROC address of the subclassed window)
	}
	else
	{
		::SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)m_pfnSuperWindowProc);
		m_pfnSuperWindowProc = ::DefWindowProc;
		m_hWnd = NULL;
	}
	
	m_bHook = FALSE;

	return hWnd;
}

/**	@brief The CMessageHook::IsHooked() function
 *	Check whether or not the hook had been installed by this CMessageHook or -derived object
 *	
 *
 *	@return BOOL TRUE if the hook had been installed by this CMessageHook or -derived object
 *
 */
BOOL CMessageHook::IsHooked() const
{
	if(m_hWnd && ::IsWindow(m_hWnd))
	{
		WNDPROC pProc = (WNDPROC)& (m_thunk.thunk);
		WNDPROC pActiveProc = (WNDPROC)::GetWindowLong(m_hWnd, GWL_WNDPROC);

		return (pProc == pActiveProc);
	}

	return FALSE;
}

/**	@brief The CMessageHook::WindowProc() function
 *	
 *	@param	hWnd [in]	a parameter of type HWND Altered 
 *	@param	nMsg [in]	a parameter of type UINT 
 *	@param	wParam [in]	a parameter of type WPARAM 
 *	@param	lParam [in]	a parameter of type LPARAM 
 *
 *	@return LRESULT CALLBACK
 */
LRESULT CALLBACK CMessageHook::WindowProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
 	CMessageHook* pThis = (CMessageHook *)hWnd;
	ASSERT(!IsBadReadPtr(pThis, sizeof(CMessageHook)));
	ASSERT(pThis);
	
	// pass to the message map to process
	LRESULT lResult = 0;
	BOOL bReturn = FALSE;
	if(pThis->m_bHook)
		bReturn = pThis->ProcessWindowMessage(nMsg, wParam, lParam, lResult);
	
	// do the default processing if message was not handled
	if (!bReturn)
	{
		lResult = pThis->DefWindowProc(nMsg, wParam, lParam);

		if (WM_NCDESTROY == nMsg && pThis->m_bAutoDelete)
			delete pThis;
	}

	return lResult;
}
